package mn.more.wits.server.dao.temp;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import mn.more.foundation.lang.StringUtil;
import mn.more.mock.io.MockModel;
import mn.more.wits.client.exception.WitsSecurityException;
import mn.more.wits.server.dao.AuthProviderDAO;
import mn.more.wits.server.model.AccountHistory;
import mn.more.wits.server.model.AccountStatus;
import mn.more.wits.server.model.AccountStatusRepository;
import mn.more.wits.server.model.ExamTaker;
import mn.more.wits.server.model.WitsRole;
import mn.more.wits.server.model.WitsUser;
import mn.more.wits.server.util.ValidationUtil;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.gwtwidgets.server.spring.ServletUtils;

/**
 * @author $Author: mikeliucc $
 * @version $Id: TestAuthProviderDAO.java 5 2008-09-01 12:08:42Z mikeliucc $
 */
public class TestAuthProviderDAO extends AbstractTestDataDAO implements AuthProviderDAO {

	private static final String KEY_ACTIVE_USERS = "activeUsers";

	private transient Logger logger = Logger.getLogger(TestAuthProviderDAO.class);

	protected Logger getLogger() { return logger; }

	public void setContext(ServletContext context) {
		super.setContext(context);
		Object o = context.getAttribute(KEY_ACTIVE_USERS);
		if (o == null) { context.setAttribute(KEY_ACTIVE_USERS, new HashMap<String, AccountHistory>()); }
	}

	public WitsUser authenticated(String username, String password) throws WitsSecurityException {
		if (debug) { refreshData(); }

		// sanity check
		// todo: add better username/password check via regex (think spring!)
		if (ValidationUtil.isBlank("username", username, logger)) { return null; }
		if (ValidationUtil.isBlank("password", password, logger)) { return null; }

		// retrieve current account model for comparison
		MockModel model = rootModel.getRefModel(username);
		if (model == null) {
			if (logger.isDebugEnabled()) { logger.debug("wrong username! No '" + username + "' found on the system."); }
			return null;
		}

		// check password
		String authCredential = model.getString("password");
		if (!authCredential.equals(password)) {
			if (logger.isDebugEnabled()) { logger.debug("wrong password! Instead use '" + authCredential + "'"); }
			return null;
		}

		// now we need to determine which type of user object to create.
		String roleName = model.getString("mainRoleName");
		WitsRole role = WitsRole.getRole(roleName);
		if (role == null) {
			if (logger.isDebugEnabled()) {
				logger.debug("invalid role '" + roleName + "' associated to user '" + username + "'.");
			}
			return null;
		}

		// todo: need better, more OO way of create wits user
		WitsUser u;
		switch (role.getId()) {
			case WitsRole.ADMINISTRATOR_ID:
				throw new WitsSecurityException("ADMINISTRATOR role currently not supported!");
			case WitsRole.EXAM_CREATOR_ID:
				throw new WitsSecurityException("EXAM CREATOR role currently not supported!");
			case WitsRole.EXAM_TAKER_ID:
				u = new ExamTaker();
				break;
			case WitsRole.INVIGILATOR_ID:
				throw new WitsSecurityException("INVILGILATOR role currently not supported!");
			default:
				throw new WitsSecurityException(roleName + " role currently not supported!");
		}

		// fill in other auth details.
		u.setLoginId(username);
		u.setDisplayName(model.getString("displayName"));
		u.setLoginTime(System.currentTimeMillis());

		String otherRoleNames = model.getString("otherRoleNames");
		if (!StringUtil.isBlank(otherRoleNames)) { u.setOtherRoleNames(StringUtils.split(otherRoleNames, ",")); }

		// store login profile in context -- to prevent duplicate login
		registerLogin(u);

		// all done
		return u;
	}

	public void disable(String username) {
		if (logger.isDebugEnabled()) { logger.debug("TestAuthProviderDAO.disable"); }

		MockModel model = rootModel.getRefModel(username);
		if (model == null) {
			if (logger.isDebugEnabled()) { logger.debug("wrong username! No '" + username + "' found on the system."); }
		} else {
			AccountStatusRepository.get().disable(username);
		}

		invalidate(username, "Account disabled");
	}

	public void enable(String username) {
		if (logger.isDebugEnabled()) { logger.debug("TestAuthProviderDAO.enable"); }
		MockModel model = rootModel.getRefModel(username);
		if (model == null) {
			if (logger.isDebugEnabled()) { logger.debug("wrong username! No '" + username + "' found on the system."); }
		} else {
			AccountStatusRepository.get().activate(username);
		}
	}

	public void invalidate(WitsUser user, String reason) {
		invalidate(user.getLoginId(), reason);
	}

	public void invalidate(String username, String reason) {
		if (logger.isDebugEnabled()) { logger.debug("TestAuthProviderDAO.invalidate"); }

		Map<String, AccountHistory> activeUsers = getActiveUsers();
		AccountHistory current = activeUsers.remove(username);
		if (current != null) {
			current.setLogoutTs(System.currentTimeMillis());
			current.setLogoutReason(reason);
			// todo: needs to "really" invalidate and persist history information
		}

		// either way we should invalidate session, just 2 b sure
		HttpServletRequest request = ServletUtils.getRequest();
		HttpSession session = request.getSession(false);
		if (session != null) { session.invalidate(); }
	}

	public AccountStatus getAccountStatus(String username) {
		if (logger.isDebugEnabled()) { logger.debug("TestAuthProviderDAO.getAccountStatus"); }
		AccountStatus status = AccountStatusRepository.get().getStatus(username);
		if (status == null) { return AccountStatus.ACTIVE; }
		return status;
	}

	public AccountHistory getInSessionDetail(String username) {
		if (logger.isDebugEnabled()) { logger.debug("enter: getInSessionDetail() - context = " + context); }
		Map<String, AccountHistory> activeUsers = getActiveUsers();
		return activeUsers.get(username);
	}

	public void changePassword(String username, String newPassword) {
		if (logger.isDebugEnabled()) { logger.debug("TestAuthProviderDAO.changePassword"); }
		MockModel model = rootModel.getRefModel(username);
		model.setValue("password", newPassword);
		try {
			writer.write(rootModel);
		} catch (IOException e) {
			logger.error("Error writing to XML", e);
		}
	}

	/** register a successful login in both context and session */
	private void registerLogin(WitsUser u) {
		HttpServletRequest request = ServletUtils.getRequest();
		u.setClientBrowser(request.getHeader("User-Agent"));
		u.setLoginIp(request.getRemoteAddr());

		HttpSession session = request.getSession();
		session.setAttribute(KEY_AUTH, u);
		// needs to reset counter
		session.removeAttribute(KEY_FAIL_COUNT);

		Map<String, AccountHistory> activeUsers = getActiveUsers();

		AccountHistory history = new AccountHistory();
		history.setClientIp(u.getLoginIp());
		history.setLoginTs(u.getLoginTime());
		history.setSessionId(session.getId());
		history.setUser(u);
		history.setUserAgent(u.getClientBrowser());

		// todo: need to persist history information

		activeUsers.put(u.getLoginId(), history);
	}

	private Map<String, AccountHistory> getActiveUsers() {
		return (Map<String, AccountHistory>) context.getAttribute(KEY_ACTIVE_USERS);
	}

}
